/***************************************************************************
 *  Title: Runtime environment 
 * -------------------------------------------------------------------------
 *    Purpose: Runs commands
 *    Author: Stefan Birrer
 *    Version: $Revision: 1.3 $
 *    Last Modification: $Date: 2009/10/12 20:50:12 $
 *    File: $RCSfile: runtime.c,v $
 *    Copyright: (C) 2002 by Stefan Birrer
 ***************************************************************************/
/***************************************************************************
 *  ChangeLog:
 * -------------------------------------------------------------------------
 *    $Log: runtime.c,v $
 *    Revision 1.3  2009/10/12 20:50:12  jot836
 *    Commented tsh C files
 *
 *    Revision 1.2  2009/10/11 04:45:50  npb853
 *    Changing the identation of the project to be GNU.
 *
 *    Revision 1.1  2005/10/13 05:24:59  sbirrer
 *    - added the skeleton files
 *
 *    Revision 1.6  2002/10/24 21:32:47  sempi
 *    final release
 *
 *    Revision 1.5  2002/10/23 21:54:27  sempi
 *    beta release
 *
 *    Revision 1.4  2002/10/21 04:49:35  sempi
 *    minor correction
 *
 *    Revision 1.3  2002/10/21 04:47:05  sempi
 *    Milestone 2 beta
 *
 *    Revision 1.2  2002/10/15 20:37:26  sempi
 *    Comments updated
 *
 *    Revision 1.1  2002/10/15 20:20:56  sempi
 *    Milestone 1
 *
 ***************************************************************************/
#define __RUNTIME_IMPL__

/************System include***********************************************/
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <dirent.h>
#include <limits.h>

/************Private include**********************************************/
#include "runtime.h"
#include "io.h"
#include "stringtokenizer.h"

/************Defines and Typedefs*****************************************/
/*  #defines and typedefs should have their names in all caps.
 *  Global variables begin with g. Global constants with k. Local
 *  variables should be in all lower case. When initializing
 *  structures and arrays, line everything up in neat columns.
 */

/************Global Variables*********************************************/

#define NBUILTINCOMMANDS (sizeof BuiltInCommands / sizeof(char*))

#define NO_REMAP -1
#define REMAP_STDIN 0
#define REMAP_STDOUT 1
#define REMAP_ALL 2
#define REDIR_STDIN 3
#define REDIR_STDOUT 4
#define REDIR_ALL 5

char* BuiltInCommands[5] = {"exit", "cd", "bg", "jobs", "fg"};

#define DONE 0
#define ABORTED 1
#define STOPPED 2
#define RUNNING 3

char* statusStrings[4] = {"Done", "Aborted", "Stopped", "Running"};

typedef struct job_l
{
  int number;
  pid_t pid;
  int status;
  char* cmdLine;
  struct job_l* next;
  struct job_l* prev;
} jobL;

/* linked-list of background processes */
jobL *firstBgJob = NULL;
jobL *lastBgJob = NULL;
int numBgJobs = 0;

jobL *fgJob = NULL;

int MAXPATHS = 256;

/************Function Prototypes******************************************/
/* run command */
static void
RunCmdFork(commandT*, bool);
/* runs an external program command after some checks */
static void
RunExternalCmd(commandT*, bool);
/* parses the PATH environment variables */
static char**
getDirectoriesFromPath();
/* checks whether the given file exists, is not a directory, and is executable */
static bool
isExecutableFile(char*);
/* resolves the path and checks for executable flag */
static bool
ResolveExternalCmd(commandT*);
/* forks and runs a external program */
static void
Exec(commandT*, bool, int, int[2], int[2]);
/* runs a builtin command */
static void
RunBuiltInCmd(commandT*);
/* checks whether a command is a builtin command */
static bool
IsBuiltIn(char*);
/* returns the job specified by the second argument in the given command */
jobL*
GetJobFromCommand(commandT*);
/* adds an ampersand (indicating a background job) to the given command */
void
AddAmpersandToCommand(char*);
/* removes ampersand and preceding whitespace from the given command */
void
RemoveAmpersandFromCommand(char*);
/* sets the given job as the foreground job and waits for it to terminate */
void
WaitForFgJob(jobL*);
/* prints the status of a particular job */
void
PrintStatus(jobL*);
/* creates a new jobL struct for the given job */
jobL*
CreateJob(int, char*);
/* adds a job to the list of background jobs */
void
AddBgJob(jobL*);
/* removes a job from the list of background jobs */
void
RemoveBgJob(jobL*);
/* frees all memory associated with the given jobL struct */
void
FreeJob(jobL*);
/* updates the given job according to the status set by waitpid */
void
UpdateJob(jobL*, int);

/************External Declaration*****************************************/

/**************Implementation***********************************************/


/*
 * RunCmd
 *
 * arguments:
 *   commandT *cmd: the command to be run
 *
 * returns: none
 *
 * Runs the given command.
 */
void
RunCmd(commandT* cmd)
{
  //printf("Executing %s in foreground\n", cmd->name);
  RunCmdFork(cmd, TRUE);
} /* RunCmd */


/*
 * RunCmdFork
 *
 * arguments:
 *   commandT *cmd: the command to be run
 *   bool foreground: whether to run the command in the foreground
 *
 * returns: none
 *
 * Runs a command, switching between built-in and external mode
 * depending on cmd->argv[0].
 */
void
RunCmdFork(commandT* cmd, bool foreground)
{
  if (cmd->argc <= 0)
    // No command to execute
    return;
  if (IsBuiltIn(cmd->name))
    {
      RunBuiltInCmd(cmd);
    }
  else
    {
      RunExternalCmd(cmd, foreground);
    }
} /* RunCmdFork */


/*
 * RunCmdBg
 *
 * arguments:
 *   commandT *cmd: the command to be run
 *
 * returns: none
 *
 * Runs a command in the background.
 */
void
RunCmdBg(commandT* cmd)
{
  //printf("Executing %s in background\n", cmd->name);
  RunCmdFork(cmd, FALSE);
} /* RunCmdBg */


/*
 * RunCmdPipe
 *
 * arguments:
 *   commandT *cmd1: the commandT struct for the left hand side of the pipe
 *   commandT *cmd2: the commandT struct for the right hand side of the pipe
 *
 * returns: none
 *
 * Runs two commands, redirecting standard output from the first to
 * standard input on the second.
 */

void
RunCmdPipe(commandT** cmd, size_t size)
{
  size_t i;
  
  for ( i = 0; i < size; i++ ) {
    if ( !ResolveExternalCmd(cmd[i]) )
      return;
  }

  int stdIn[2];
  int stdOut[2];

  stdIn[0] = 0xdeadbeef;
  stdIn[1] = 0xdeadbeef;

  for ( i = 0; i < size; i++ ) {
    if ( i > 1 ) {
      close(stdIn[0]);
      close(stdIn[1]);
    }

    if ( i != 0 ) {
      stdIn[0] = stdOut[0];
      stdIn[1] = stdOut[1];
    }    

    if ( i != size - 1 && pipe( stdOut ) < 0 )
      exit(1);

    if ( i == 0 ) {
      Exec(cmd[i], TRUE, REMAP_STDOUT, NULL, stdOut);
    } else if ( i == size - 1 ) {
      Exec(cmd[i], TRUE, REMAP_STDIN, stdIn, NULL);
    } else {
      Exec(cmd[i], TRUE, REMAP_ALL, stdIn, stdOut);
    }
  }

  close(stdIn[0]);
  close(stdIn[1]);

  for( i = 0; i < size; i++ )
    wait(0);
} /* RunCmdPipe */


/*
 * RunCmdRedirOut
 *
 * arguments:
 *   commandT *cmd: the command to be run
 *   char *file: the file to be used for standard output
 *
 * returns: none
 *
 * Runs a command, redirecting standard output to a file.
 */
void
RunCmdRedirOut(commandT* cmd, char* file)
{
  if ( !ResolveExternalCmd(cmd) )
    return;

  struct StringTokens * tokens = tokens_create( file, " " );

  int filedes[2];
  filedes[1] = open( tokens_get(tokens, 0), O_CREAT | O_WRONLY | O_TRUNC, 0644 );

  Exec(cmd, TRUE, REDIR_STDOUT, NULL, filedes);
  
  close(filedes[1]);

  tokens_free( tokens );
  wait(0);
} /* RunCmdRedirOut */


/*
 * RunCmdRedirIn
 *
 * arguments:
 *   commandT *cmd: the command to be run
 *   char *file: the file to be used for standard input
 *
 * returns: none
 *
 * Runs a command, redirecting a file to standard input.
 */
void
RunCmdRedirIn(commandT* cmd, char* file)
{
  if ( !ResolveExternalCmd(cmd) )
    return;

  struct StringTokens * tokens = tokens_create( file, " " );
  
  int filedes[2];
  filedes[0] = open( tokens_get(tokens, 0), O_RDONLY, 0644 );

  Exec(cmd, TRUE, REDIR_STDIN, filedes, NULL);

  close(filedes[0]);

  tokens_free( tokens );
  wait(0);
}  /* RunCmdRedirIn */

void
RunCmdRedirAll(commandT* cmd, char* file1, char* file2)
{
  if ( !ResolveExternalCmd(cmd) )
    return;

  struct StringTokens * tokens1 = tokens_create( file1, " " );
  struct StringTokens * tokens2 = tokens_create( file2, " " );  

  int filedes[2];
  filedes[0] = open( tokens_get(tokens1, 0), O_RDONLY, 0644 );
  filedes[1] = open( tokens_get(tokens2, 0), O_CREAT | O_WRONLY | O_TRUNC, 0644 );

  Exec(cmd, TRUE, REDIR_ALL, filedes, filedes );

  close(filedes[0]);
  close(filedes[1]);

  tokens_free( tokens1 );
  tokens_free( tokens2 );
  wait(0);
}

/*
 * RunExternalCmd
 *
 * arguments:
 *   commandT *cmd: the command to be run
 *   bool fork: whether to fork
 *
 * returns: none
 *
 * Tries to run an external command.
 */
static void
RunExternalCmd(commandT* cmd, bool fork )
{
  if (ResolveExternalCmd(cmd))
    {
      Exec(cmd, fork, NO_REMAP, NULL, NULL);
    }
  else
    {
      // TODO: The "current line" number needs to be properly parameterized
      // once we add support for shell scripts
      char str[MAXLINE];
      snprintf(str, MAXLINE - 1, "line %d: %s", 1, cmd->name);
      PrintPError(str);
    }
}  /* RunExternalCmd */


/*
 * ResolveExternalCmd
 *
 * arguments:
 *   commandT *cmd: the command to be run
 *
 * returns: bool: whether the given command exists
 *
 * Determines whether the command to be run actually exists.
 */
static bool
ResolveExternalCmd(commandT* cmd)
{
  bool result = FALSE;

  // Handle commands specified with an absolute path
  if (cmd->name[0] == '/' && isExecutableFile(cmd->name))
    {
      cmd->path = cmd->name;
      result = TRUE;
    }
  else
    {
      char** paths = NULL;

      // If the command starts with "./", search the current working directory.
      // Otherwise, search for the command within the directories defined in the
      // PATH environment variable
      if (strstr(cmd->name, "./") == cmd->name)
        {
          // TODO: Add support for shell scripts
          paths = getCurrentWorkingDir();
        }
      else
        {
          paths = getDirectoriesFromPath();
        }

      if (paths == NULL)
        {
          return FALSE;
        }

      char* dirPath = 0;
      char* filePath = malloc(sizeof (char*) * PATH_MAX);
      filePath[0] = 0;
      int i = 0;
      while ((dirPath = paths[i]) != NULL)
        {
          sprintf(filePath, "%s/%s", dirPath, cmd->name);
          if (!result && isExecutableFile(filePath))
            {
              cmd->path = malloc(sizeof (char*) * PATH_MAX);
              strcpy(cmd->path, filePath);
              result = TRUE;
            }
          free(dirPath);
          dirPath = paths[i] = NULL;
          i++;
        }
      free(paths);
      free(filePath);
    }

  return result;
} /* ResolveExternalCmd */


/*
 * getCurrentWorkingDir
 *
 * arguments: none
 *
 * returns: char**: pointer to a string containing the current working directory
 *
 * Gets the current working directory.
 */
char**
getCurrentWorkingDir()
{
  char** cwd = malloc(sizeof (char*) * 2);
  cwd[0] = malloc(sizeof (char) * PATH_MAX);
  cwd[1] = NULL;

  if (getcwd(cwd[0], PATH_MAX) == NULL)
    {
      free(cwd[0]);
      free(cwd);
      return NULL;
    }

  return cwd;
}  /* getCurrentWorkingDir */


/*
 * getDirectoriesFromPath
 *
 * arguments: none
 *
 * returns: char**: array of pathnames
 *
 * Parses the pathnames contained in the PATH environment variable.
 * This function is adapted from getCommand() in interpreter.c
 */
static char**
getDirectoriesFromPath()
{
  char** paths = malloc(sizeof (char*) * MAXPATHS);
  paths[0] = 0;
  int i = 0,
      inArg = 0,
      numPaths = 0;

  char* pathVariable = getenv("PATH");
  if (pathVariable == NULL)
    {
      free(paths);
      return NULL;
    }

  // Set up the initial empty path variable
  char* tmp = malloc(sizeof (char*) * PATH_MAX);
  int tmpLen = 0;
  tmp[0] = 0;

  // Parse the PATH environment variable
  for (i = 0; pathVariable[i] != 0; i++)
    {
      //printf("\tindex %d, char %c\n", i, cmdLine[i]);

      // Check for whitespace
      if (pathVariable[i] == ':')
        {
          if (inArg == 0)
            continue;
          //printf("\t\tend of argument %d, got:%s\n", cmd.argc, tmp);
          paths[numPaths] = malloc(sizeof (char) * (tmpLen + 1));
          strcpy(paths[numPaths], tmp);

          inArg = 0;
          tmp[0] = 0;
          tmpLen = 0;
          numPaths++;
          paths[numPaths] = 0;
          continue;
        }

      // If we get here, we're in text or a quoted string
      inArg = 1;

      tmp[tmpLen++] = pathVariable[i];
      tmp[tmpLen] = 0;
    }
  // End the final argument, if any.
  if (tmpLen > 0)
    {
      //printf("\t\tend of argument %d, got:%s\n", cmd.argc, tmp);
      paths[numPaths] = malloc(sizeof (char) * (tmpLen + 1));
      strcpy(paths[numPaths], tmp);

      inArg = 0;
      tmp[0] = 0;
      tmpLen = 0;
      numPaths++;
      paths[numPaths] = 0;
    }

  free(tmp);

  return paths;
} /* getDirectoriesFromPath */


/*
 * isExecutableFile
 *
 * arguments: char*: pathname for the file to check
 *
 * returns: bool: whether the function is a valid executable file
 *
 * Checks whether the given file exists, is not a directory, and is executable.
 */
static bool
isExecutableFile(char* filePath)
{
  struct stat file;
  if (stat(filePath, &file) == 0)
    {
      if (S_ISREG(file.st_mode) && (access(filePath, X_OK) == 0))
        {
          return TRUE;
        }
    }
  return FALSE;
} /* isExecutableFile */


/*
 * Exec
 *
 * arguments:
 *   commandT *cmd: the command to be run
 *   bool background: whether to run the command in the background
 *
 * returns: none
 *
 * Executes a command.
 */
static void
Exec(commandT* cmd, bool foreground, int remap, int stdIn[2], int stdOut[2])
{
  sigset_t signalSet; 

  if ( remap == NO_REMAP ) {
    // Block SIGCHLD signal
    if (sigemptyset(&signalSet) < 0
        || sigaddset(&signalSet, SIGCHLD) < 0
        || sigprocmask(SIG_BLOCK, &signalSet, NULL) < 0)
      {
        Print("Error: Failed to block SIGCHLD before forking");
        return;
      }
  }

  // Create a child process to run the shell program
  int pid = fork();

  if (pid < 0)
    {
      Print("Error: Failed to create a new process");
      return;
    }
  else if (pid == 0)
    {
      switch( remap ) {
        case REMAP_STDOUT:
          dup2(stdOut[1], STDOUT_FILENO);
          close(stdOut[0]);
          close(stdOut[1]);
          break;
        case REMAP_STDIN:
          dup2(stdIn[0], STDIN_FILENO);
          close(stdIn[0]);
          close(stdIn[1]);
          break;
        case REMAP_ALL:
          dup2(stdOut[1], STDOUT_FILENO);
          dup2(stdIn[0], STDIN_FILENO);
          close(stdOut[0]);
          close(stdOut[1]);
          close(stdIn[0]);
          close(stdIn[1]);
          break;

        case REDIR_STDOUT:
          dup2(stdOut[1], STDOUT_FILENO);
          close(stdOut[1]);
          break;
        case REDIR_STDIN:
          dup2(stdIn[0], STDIN_FILENO);
          close(stdIn[0]);
          break;
        case REDIR_ALL:
          dup2(stdOut[1], STDOUT_FILENO);
          dup2(stdIn[0], STDIN_FILENO);
          close(stdOut[1]);
          close(stdIn[0]);
          break;                   
      }
      // CHILD PROCESS

      // Change the process-group so SIGINT signals get delivered correctly.
      // This command sets the child's process group ID to the child's PID.
      setpgid(0, 0);

      // Unblock SIGCHLD
      if (sigprocmask(SIG_UNBLOCK, &signalSet, NULL) < 0)
        {
          Print("Error: Child process failed to unblock SIGCHLD");
        }

      // Attempt to run the command
      if (execv(cmd->path, cmd->argv) == -1)
        {
          // If the command fails to execute,
          // return 1 to indicate an error after creating a new process.
          Print("Error: Failed to execute command");
          exit(1);
        }
    }

if ( remap == NO_REMAP ) {
  // PARENT PROCESS (SHELL)

  jobL* job = CreateJob(pid, cmd->cmdLine);

  if ( ! foreground)
    {
      AddBgJob(job);
    }

  // Unblock SIGCHLD after adding background jobs to the job list
  // in order to avoid a race condition
  if (sigprocmask(SIG_UNBLOCK, &signalSet, NULL) < 0)
    {
      Print("Error: Shell process failed to unblock SIGCHLD");
    }      

  if (foreground)
    {
      WaitForFgJob(job);
    }
}
} /* Exec */


/*
 * IsBuiltIn
 *
 * arguments:
 *   char *cmd: a command string (e.g. the first token of the command line)
 *
 * returns: bool: TRUE if the command string corresponds to a built-in
 *                command, else FALSE.
 *
 * Checks whether the given string corresponds to a supported built-in
 * command.
 */
static bool
IsBuiltIn(char* cmd)
{
  int i;
  for (i = 0; i < NBUILTINCOMMANDS; i++)
    {
      if (strcmp(cmd, BuiltInCommands[i]) == 0)
        {
          return TRUE;
        }
    }
  return FALSE;
} /* IsBuiltIn */


/*
 * RunBuiltInCmd
 *
 * arguments:
 *   commandT *cmd: the command to be run
 *
 * returns: none
 *
 * Runs a built-in command.
 */
static void
RunBuiltInCmd(commandT* cmd)
{
  // exit Handler
  if (strcmp(cmd->argv[0], "exit") == 0)
    {
      forceExit = TRUE;
    }

  // cd Handler
  else if (strcmp(cmd->argv[0], "cd") == 0)
    {
      if (cmd->argv[1] != NULL)
        {
          if (chdir(cmd->argv[1]) == -1)
            {
              // Failed to change directory
              // Print the appropriate error message, prefixed with "cd: DIRECTORY"
              char str[MAXLINE];
              snprintf(str, MAXLINE - 1, "cd: %s", cmd->argv[1]);
              PrintPError(str);
            }
        }
    }

  /*
   * fg Handler
   */
  else if (strcmp(cmd->argv[0], "fg") == 0)
    {
      jobL* job = GetJobFromCommand(cmd);
      if (job != NULL)
        {
          RemoveBgJob(job);
          kill((job->pid * -1), SIGCONT);
          WaitForFgJob(job);
        }
    }
  
  /*
   * bg Handler
   */
  else if (strcmp(cmd->argv[0], "bg") == 0)
    {
      jobL* job = GetJobFromCommand(cmd);
      if (job != NULL)
        {
          kill((job->pid * -1), SIGCONT);

          // Add an ampersand to the command to indicate
          // that it is running in the background
          AddAmpersandToCommand(job->cmdLine);
          job->status = RUNNING;
        }
    }

  /*
   * jobs Handler
   */
  else if (strcmp(cmd->argv[0], "jobs") == 0)
    {
      // Print each job's status
      int jobNumber;
      jobL *job = firstBgJob;
      for (jobNumber = 1; jobNumber <= numBgJobs; jobNumber++)
        {
          PrintStatus(job);
          job = job->next;
        }
    }
} /* RunBuiltInCmd */


/*
 * CheckJobs
 *
 * arguments: none
 *
 * returns: none
 *
 * Prints the status of jobs that have finished normally or abnormally
 */
void
CheckJobs()
{
  int jobNumber, lastBgJobs = numBgJobs;
  jobL *job = firstBgJob, *nextJob;

  // Print each finished job's status and remove it from the job list
  for (jobNumber = 1; jobNumber <= lastBgJobs; jobNumber++)
    {
      if (job->status == DONE || job->status == ABORTED)
        {
          PrintStatus(job);
          nextJob = job->next;
          RemoveBgJob(job);
          FreeJob(job);
          job = nextJob;
        }
      else
        {
          job = job->next;
        }
    }
} /* CheckJobs */


/*
 * GetJobFromCommand
 *
 * arguments: commandT *cmd: the current command being parsed
 *
 * returns: Pointer to the job struct for the specified job
 *
 * Returns the job specified by the second argument in the given command
 * (e.g. "fg 2" will return the second job in the job list)
 */
jobL*
GetJobFromCommand(commandT* cmd)
{
  jobL* job;
  if (cmd->argc == 1)
    {
      job = lastBgJob;
    }
  else
    {
      int i, jobNumber = atoi(cmd->argv[1]);
      job = firstBgJob;
      if (jobNumber <= 0 || jobNumber > numBgJobs)
        {
          return NULL;
        }
      for (i = 1; i < jobNumber; i++)
        {
          job = job->next;
        }
    }
  if (job == NULL)
    {
      char str[MAXLINE];
      snprintf(str, MAXLINE - 1, "%s: current: no such job", cmd->name);
    }
  return job;
}


/*
 * AddAmpersandToCommand
 *
 * arguments: char *cmd: the command string to modify
 *
 * returns: none
 *
 * Adds an ampersand (indicating a background job) to the given command
 */
void
AddAmpersandToCommand(char* command)
{
  // Remove any trailing whitespace
  int lastChar = strlen(command) - 1;
  while (command[lastChar] == ' ')
    {
      command[lastChar] = '\0';
      lastChar--;
    }

  // Add the ampersand
  if ( command[ strlen(command) - 1 ] != '&' )
    {
      sprintf(command, "%s &", command);
    }
}


/*
 * RemoveAmpersandFromCommand
 *
 * arguments: char *cmd: the command string to modify
 *
 * returns: none
 *
 * Removes an ampersand and preceding whitespace from the given command
 */
void
RemoveAmpersandFromCommand(char* command)
{
  int lastChar = strlen(command) - 1;
  if (command[lastChar] == '&')
    {
      do {
          command[lastChar] = '\0';
          lastChar--; 
        }
      while (command[lastChar] == ' ');
    }
}


/*
 * WaitForFgJob
 *
 * arguments: jobL* job: the foreground job to wait for
 *
 * returns: none
 *
 * Sets the given job as the foreground job and waits for it to terminate
 */
void
WaitForFgJob(jobL* job)
{
  fgJob = job;
  while (fgJob != NULL)
    {
      sleep(1);
    }
}


/*
 * PrintStatus
 *
 * arguments: jobL* job: the job to print the status of
 *
 * returns: none
 *
 * Prints the job number, status, and command associated
 * with a particular background job
 */
void
PrintStatus(jobL* job)
{
  printf("[%d]   %-25s%s\n", job->number, statusStrings[job->status], job->cmdLine);
  fflush(stdout);
}


/*
 * CreateJob
 *
 * arguments: int pid: the job's process ID
 *            char* commandLine: the original command line that started the job
 *
 * returns: jobL*: pointer to a struct representing the given job
 *
 * Creates a new jobL struct for the given job
 */
jobL*
CreateJob(int pid, char* commandLine)
{
  jobL* job = malloc(sizeof(jobL));
  job->pid = pid;
  job->status = RUNNING;

  job->cmdLine = malloc(sizeof(char*) * BUFSIZE);
  strcpy(job->cmdLine, commandLine);

  return job;
}


/*
 * AddBgJob
 *
 * arguments: jobL* job: the job to add to the job list
 *
 * returns: none
 *
 * Adds a job to the list of background jobs
 */
void
AddBgJob(jobL* job)
{
  if (numBgJobs == 0)
    {
      job->prev = NULL;
      firstBgJob = job;
    }
  else
    {
      job->prev = lastBgJob;
      lastBgJob->next = job;
    }

  job->next = NULL;
  lastBgJob = job;

  numBgJobs++;
  job->number = numBgJobs;
}


/*
 * RemoveBgJob
 *
 * arguments: jobL* job: the job to remove from the job list
 *
 * returns: none
 *
 * Removes a job from the list of background jobs
 */
void
RemoveBgJob(jobL* job)
{
  if (job == firstBgJob)
    {
      firstBgJob = job->next;
    }

  if (job == lastBgJob)
    {
      lastBgJob = job->prev;
    }

  if (job->prev != NULL)
    {
      job->prev->next = job->next;
    }

  if (job->next != NULL)
    {
      job->next->prev = job->prev;
    }
  numBgJobs--;
}


/*
 * FreeJob
 *
 * arguments: jobL* job: pointer to the jobL struct to free
 *
 * returns: none
 *
 * Frees all memory associated with the given jobL struct
 */
void
FreeJob(jobL* job)
{
  if (job->cmdLine != NULL)
    {
      free(job->cmdLine);
    }

  job->cmdLine = NULL;
  job->next = NULL;
  job->prev = NULL;

  free(job);
}


/*
 * ReapChildProc
 *
 * arguments: none
 *
 * returns: none
 *
 * Reaps any child process that has been terminated or stopped
 * and updates the corresponding job (foreground or background)
 */
void
ReapChildProc()
{
  pid_t pid;
  int status;
  jobL* job = NULL;

  if ( (pid = waitpid(-1, &status, WNOHANG | WUNTRACED | WCONTINUED)) == -1 )
    {
      //Print("Error waiting for process to terminate");
    }
  else
    {
      // Find the job struct corresponding to the reaped child
      if (fgJob != NULL && pid == fgJob->pid)
        {
          job = fgJob;
        }
      else
        {
          job = firstBgJob;
          while (job != NULL)
            {
              if (job->pid == pid)
                {
                  break;
                }
              job = job->next;
            }
        }

      if (job != NULL)
        {
          UpdateJob(job, status);
        }
    }
}


/*
 * UpdateJob
 *
 * arguments: jobL* job: pointer to the job struct to update
 *            int status: the status set by waitpid for the given process
 *
 * returns: none
 *
 * Updates the given job according to the status set by waitpid
 */
void
UpdateJob(jobL* job, int status)
{
  bool foreground = (fgJob != NULL && job->pid == fgJob->pid);

  if ( WIFEXITED(status) || WIFSIGNALED(status) )
    {
      if (foreground)
        {
          FreeJob(fgJob);
          fgJob = NULL;
        }
      else
        {
          job->status = DONE;
          RemoveAmpersandFromCommand(job->cmdLine);
        }
    }
  else if ( WIFSTOPPED(status) )
    {
      job->status = STOPPED;
      RemoveAmpersandFromCommand(job->cmdLine);

      if (foreground)
        {
          AddBgJob(fgJob);
          PrintNewline();
          PrintStatus(fgJob);
          fgJob = NULL;
        }
    }
  else if ( WIFCONTINUED(status) )
    {
      job->status = RUNNING;
    }
}


/*
 * SignalFgProc
 *
 * arguments: int signo: The signal to send to the process
 *
 * returns: none
 *
 * Sends the specified signal to the current foreground process group,
 * if there is one.
 */
void
SignalFgProc(int signo)
{
  if (fgJob != NULL)
    {
      kill((fgJob->pid * -1), signo);
    }
} /* StopFgProc */
